import fs from 'fs';
import request from 'request';
import thunkify from 'thunkify';
import mkdirp from 'mkdirp';
import md5 from 'md5';


const REQ_TIMEOUT = 300;
const ISO8601_DATE_FORMAT = 'yyyy-MM-dd hh:mm:ss.SSS';

/**
 * Kutils
 */
class Kutils {
    tryUntilSuccess({ ctx, limit = 5, delay = 1000, done = null }, run, ...args) {
        const runName = run.name;
        const runHash = md5(`${runName}::${args}`);
        const contextParent = ctx;
        if (!contextParent[runHash]) contextParent[runHash] = {};
        const context = contextParent[runHash];

        if (!context.retry) context.retry = 0;

        if (context.retry >= limit) {
            context.retry = 0;
            clearTimeout(context.retryTimeout);
            if (done) done();
        } else {
            context.retry++;
            context.retryTimeout = setTimeout(() => {
                console.log(
                    '[try_again(%d/%d)] %s(%s)',
                    context.retry,
                    limit,
                    runName,
                    args
                );

                run.call(ctx, ...args);
            }, delay);
        }
    }

    writeFile({ content, dir, fileName }, callback) {
        const fileRealName = `${dir}/${fileName}`;

        mkdirp.sync(dir);

        fs.writeFile(fileRealName, content, 'utf8', (err) => {
            if (err) {
                callback.call(null, 'write file error', err);
            } else {
                callback.call(null, 'ok');
            }
        });
    }

    * writeFileSync(content, dir, fileName) {
        const syncFunc = thunkify(this.writeFile);
        yield syncFunc({ content, dir, fileName });
    }

    download({ uri, dir, fileName }, callback) {
        // const self = this;

        request({
            method: 'HEAD',
            uri,
            timeout: REQ_TIMEOUT,
        }, (err, res) => {
            if (err || !res) {
                // callback.call(null, 'head failed', err);
                return;
            }

            const contentType = res ? res.headers['content-type'] : null;
            let ext = contentType ? contentType.split('/')[1] : 'jpg';
            if (ext === 'jpeg') ext = 'jpg';
            const fileRealName = `${dir}/${fileName}.${ext}`;
            const contentLength = res ? res.headers['content-length'] : 0;

            // console.log(`HEAD ${fileRealName}(${uri}); ${contentType},${contentLength}`);

            if (!contentType || !contentLength) {
                callback.call(null, 'empty content', true);
                return;
            }

            mkdirp.sync(dir);

            const fstream = fs
                .createWriteStream(fileRealName)
                .on('error', callback.bind(null, 'fstream error'));

            request({
                method: 'GET',
                uri,
                timeout: REQ_TIMEOUT,
            }).on('error', callback.bind(null, 'request error'))
            .pipe(fstream)
            .on('close', callback.bind(null, 'ok'));
        })
        .on('error', callback.bind(null, 'head error'));
    }

    * downloadSync(uri, dir, fileName) {
        const syncFunc = thunkify(this.download);
        yield syncFunc({ uri, dir, fileName });
    }

    padWithZeros(num, width) {
        let numAsString = `${num}`;
        while (numAsString.length < width) {
            numAsString = `0${numAsString}`;
        }
        return numAsString;
    }

    addZero(num) {
        return this.padWithZeros(num, 2);
    }

    dateOffset(date) {
        // Difference to Greenwich time (GMT) in hours
        const os = Math.abs(date.getTimezoneOffset());
        let h = String(Math.floor(os / 60));
        let m = String(os % 60);
        if (h.length === 1) {
            h = `0${h}`;
        }
        if (m.length === 1) {
            m = `0${m}`;
        }
        return date.getTimezoneOffset() < 0 ? `+${h}${m}` : `-${h}${m}`;
    }

    dateFormat(date, format = ISO8601_DATE_FORMAT) {
        const day = this.addZero(date.getDate());
        const month = this.addZero(date.getMonth() + 1);
        const yearLong = this.addZero(date.getFullYear());
        const yearShort = this.addZero(date.getFullYear().toString().substring(2, 4));
        const year = (format.indexOf('yyyy') > -1 ? yearLong : yearShort);
        const hour = this.addZero(date.getHours());
        const minute = this.addZero(date.getMinutes());
        const second = this.addZero(date.getSeconds());
        const millisecond = this.padWithZeros(date.getMilliseconds(), 3);
        const timeZone = this.dateOffset(date);
        const formatted = format
            .replace(/dd/g, day)
            .replace(/MM/g, month)
            .replace(/y{1,4}/g, year)
            .replace(/hh/g, hour)
            .replace(/mm/g, minute)
            .replace(/ss/g, second)
            .replace(/SSS/g, millisecond)
            .replace(/O/g, timeZone);
        return formatted;
    }

    getTodayZeroSeconds() {
        const d = new Date();
        return new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1).getTime() / 1000;
    }

    getNowSeconds() {
        return Date.parse(new Date()) / 1000;
    }

    formatStr(format, ...args) {
        let i = -1;
        const a = format.replace(/\{\}/g, () => {
            i++;
            return args[i];
        });
        return a;
    }
}

export default Kutils;
